﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.IO;
using System.Drawing;
using System.Threading.Tasks;
namespace GPC_Overlay
{
    public class Parser
    {
        public List<Polygon> poly { get; set; }

        public Parser(string inputFile, string outputFile)
        {
            using (XmlTextReader reader = new XmlTextReader(inputFile))
            {
                _parse(reader, outputFile);
            }
        }

        public Parser(string inputFile)
        {
            using (XmlTextReader reader = new XmlTextReader(inputFile))
            {
                _parse(reader);
            }
        }

        public Parser(MemoryStream strm)
        {
            using (XmlTextReader reader = new XmlTextReader(strm))
            {
                _parse(reader);
            }
        }

        public void _parse(XmlTextReader myParser, string outputFile)
        {
            StringBuilder output = new StringBuilder();
            Polygon currentPolygon = null;
            int ID = 0; /* Unique Id for each polygon */
            poly = new List<Polygon>();
            int isPoly = 0;
            long numContours = 0;

            using (StreamWriter sw = File.CreateText(outputFile))
            {
                sw.WriteLine(numContours.ToString());
                /* Read the XML elements */

                while (myParser.Read())
                {

                    /* There are two kind of elements in the world. The substantial ones called polygons,
                     * and the hideous ones called holes. Both have pos lists but the element outside 
                     * Linearring appears to be different. It is exterior for the polygons and interior 
                     * for the holes. A good man should read both and discriminate*/

                    if (myParser.Name.Contains("multipolygons") && myParser.IsStartElement())
                    {
                        currentPolygon = new Polygon();
                        currentPolygon.ID = ID;
                        ID = ID + 1;
                    }

                    if (myParser.Name.Contains("multipolygons") && (!myParser.IsStartElement()))
                    {
                        poly.Add(currentPolygon);
                    }


                    if (myParser.Name.Contains("exterior"))
                    {
                        isPoly = 1;
                        continue;
                    }

                    if (myParser.Name.Contains("interior"))
                    {
                        isPoly = 0;
                        continue;
                    }

                    /* if this is the list of points  - Bingo */
                    if (myParser.Name.Contains("posList"))
                    {
                        float lowerboxX, lowerboxY, upperboxX, upperboxY;
                        if (isPoly == 1)
                        {
                            /* export to file if we need to write it to file too */
                            sw.WriteLine("0");
                            PointF[] vertices = parsePts(myParser.ReadString(), sw, out lowerboxX, out lowerboxY, out upperboxX, out upperboxY).ToArray();
                            currentPolygon.upperBBox.X = upperboxX;
                            currentPolygon.upperBBox.Y = upperboxY;
                            currentPolygon.lowerBBox.X = lowerboxX;
                            currentPolygon.lowerBBox.Y = lowerboxY;

                            currentPolygon.AddContour(new VertexList(vertices), false);
                        }
                        else
                        {
                            sw.WriteLine("1");
                            PointF[] vertices = parsePts(myParser.ReadString(), sw, out lowerboxX, out lowerboxY, out upperboxX, out upperboxY).ToArray();
                            currentPolygon.AddContour(new VertexList(vertices), true);
                        }
                    }
                }
            }

        }

        public void _parse(XmlTextReader myParser)
        {
            Polygon currentPolygon = null;

            int ID = 0;
            poly = new List<Polygon>();
            int isPoly = 0;

            /* If we store everything in memory and then flush to a file we choke up. Therefore, we keep flushing to a file - don't "byte :)" more than you can chew*/

            while (myParser.Read())
            {

                /* There are two kind of elements in the world. The substantial ones called polygons,
                    * and the hideous ones called holes. Both have pos lists but the element outside 
                    * Linearring appears to be different. It is exterior for the polygons and interior 
                    * for the holes. A good man should read both and discriminate*/

                if (myParser.Name.Contains("featureMember") && myParser.IsStartElement())
                {
                    currentPolygon = new Polygon();
                    currentPolygon.ID = ID;
                    ID = ID + 1;
                }

                else if (myParser.Name.Contains("featureMember") && (!myParser.IsStartElement()))
                {
                    poly.Add(currentPolygon);
                }

                else if (myParser.Name.Contains("FID") && myParser.IsStartElement())
                {
                    currentPolygon.fID = myParser.ReadString();
                }

                else if (myParser.Name.Contains("Type") && myParser.IsStartElement())
                {
                    currentPolygon.polyType = myParser.ReadString();
                }

                else if (myParser.Name.Contains("Surface") && myParser.IsStartElement())
                {
                    if (myParser.AttributeCount >= 2)
                    {
                        currentPolygon.srsname = myParser[0];
                        currentPolygon.srsDimension = myParser[1];
                    }
                }

                /* Check if this is the polygon or a hole */
                else if (myParser.Name.Contains("exterior"))
                {
                    /* It's a polygon */
                    isPoly = 1;
                    continue;
                }

                else if (myParser.Name.Contains("interior"))
                {
                    /* It is a hole */
                    isPoly = 0;
                    continue;
                }

                /* if this is the list of points  - Bingo */
                else if (myParser.Name.Contains("posList"))
                {
                    float lowerboxX, lowerboxY, upperboxX, upperboxY;
                    if (isPoly == 1) /* It is a hole */
                    {
                        //PointF[] vertices = parsePts(myParser.ReadString()).ToArray();
                        PointF[] vertices = parsePts(myParser.ReadString(), out lowerboxX, out lowerboxY, out upperboxX, out upperboxY).ToArray();
                        currentPolygon.upperBBox.X = upperboxX;
                        currentPolygon.upperBBox.Y = upperboxY;
                        currentPolygon.lowerBBox.X = lowerboxX;
                        currentPolygon.lowerBBox.Y = lowerboxY;

                        currentPolygon.AddContour(new VertexList(vertices), false);
                    }
                    else
                    {
                        //PointF[] vertices = parsePts(myParser.ReadString()).ToArray();
                        PointF[] vertices = parsePts(myParser.ReadString(), out lowerboxX, out lowerboxY, out upperboxX, out upperboxY).ToArray();
                        currentPolygon.upperBBox.X = upperboxX;
                        currentPolygon.upperBBox.Y = upperboxY;
                        currentPolygon.lowerBBox.X = lowerboxX;
                        currentPolygon.lowerBBox.Y = lowerboxY;

                        currentPolygon.AddContour(new VertexList(vertices), true);
                    }
                }
            }
        }

        public static void Save(List<Polygon> poly, string outputfile)
        {
            StreamWriter file = new StreamWriter(outputfile);
            StringBuilder result = new StringBuilder();

            /* Write the start of XML doc */
            result.AppendLine("<gml:FeatureCollection");

            result.AppendLine(" xmlns:gml=" + '"' + "http://www.opengis.net/gml" + '"');
            result.AppendLine(" xmlns:xlink=" + '"' + "http://www.w3.org/1999/xlink" + '"');
            result.AppendLine(" xmlns:xsi=" + '"' + "http://www.w3.org/2001/XMLSchema-instance" + '"');
            result.AppendLine(" xmlns:fme=" + '"' + "http://www.safe.com/gml/fme" + '"');
            result.AppendLine(" xsi:schemaLocation=" + '"' + "http://www.safe.com/gml/output.xsd>" + '"');
            try
            {
                foreach (Polygon polygon in poly)
                {
                    result.Append(Parser.PolygonToString(polygon));
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
            }

            result.AppendLine("</gml:FeatureCollection>");
            file.WriteLine(result.ToString());
            file.Close();
        }

        List<PointF> parsePts(string posList, StreamWriter sw, out float lowerboxX, out float lowerboxY, out float upperboxX, out float upperboxY)
        {
            /* Read the values of the points inside the polygon */
            String[] points = posList.Split(' ');

            List<PointF> vertexList = new List<PointF>();
            lowerboxX = float.Parse(points[0]);
            lowerboxY = float.Parse(points[1]);
            upperboxX = float.Parse(points[0]);
            upperboxY = float.Parse(points[1]);

            for (int i = 0; i < points.Length - 2; i = i + 2)
            {
                sw.Write(points[i]);
                sw.Write(" ");
                sw.Write(points[i + 1]);
                /* Add this point (x and y) to the vertexlist */
                vertexList.Add(new PointF(float.Parse(points[i]), float.Parse(points[i + 1])));

                /* update the bounding box */
                if (float.Parse(points[i]) < lowerboxX) lowerboxX = float.Parse(points[i]);
                if (float.Parse(points[i + 1]) < lowerboxY) lowerboxY = float.Parse(points[i + 1]);
                if (float.Parse(points[i]) > upperboxX) upperboxX = float.Parse(points[i]);
                if (float.Parse(points[i + 1]) > upperboxY) upperboxY = float.Parse(points[i + 1]);

            }
            return vertexList;
        }

        public static List<PointF> parsePts(string posList, out float lowerboxX, out float lowerboxY, out float upperboxX, out float upperboxY)
        {
            /* Read the values of the points inside the polygon */
            String[] points = posList.Split(' ');
            List<PointF> vertexList = new List<PointF>();
            lowerboxX = float.Parse(points[0]);
            lowerboxY = float.Parse(points[1]);
            upperboxX = float.Parse(points[0]);
            upperboxY = float.Parse(points[1]);


            for (int i = 0; i < points.Length - 2; i = i + 2)
            {
                /* Add this point (x and y) to the vertexlist */
                vertexList.Add(new PointF(float.Parse(points[i]), float.Parse(points[i + 1])));

                /* update the bounding box */
                if (float.Parse(points[i]) < lowerboxX) lowerboxX = float.Parse(points[i]);
                if (float.Parse(points[i + 1]) < lowerboxY) lowerboxY = float.Parse(points[i + 1]);
                if (float.Parse(points[i]) > upperboxX) upperboxX = float.Parse(points[i]);
                if (float.Parse(points[i + 1]) > upperboxY) upperboxY = float.Parse(points[i + 1]);
            }
            return vertexList;
        }

        public static List<PointF> parsePts(string posList)
        {
            /* Read the values of the points inside the polygon */
            String[] points = posList.Split(' ');
            List<PointF> vertexList = new List<PointF>();

            for (int i = 0; i < points.Length - 2; i = i + 2)
            {
                /* Add this point (x and y) to the vertexlist */
                vertexList.Add(new PointF(float.Parse(points[i]), float.Parse(points[i + 1])));
            }
            return vertexList;
        }

        public static void createRelationships(ref List<Polygon> basePolygon, ref List<Polygon> overlayPolygon)
        {
            float baseBox = 0F;
            int range;
            for (int i = 0; i < basePolygon.Count; i++)
            {
                baseBox = basePolygon[i].upperBBox.X;
                /* Find the polygon with index where base's upperBox is no less than overlay's lower box */
                range = BinarySearch(overlayPolygon.ToArray(), baseBox);
                List<Polygon> Remaining = overlayPolygon.GetRange(0, range);

                foreach (Polygon p in Remaining)
                {
                    if (isOverlap(basePolygon[i], p))
                    {
                        basePolygon[i].LinkPolygon(p);
                    }
                }
            }
        }

        static bool isOverlap(Polygon p1, Polygon p2)
        {
            if (p2.lowerBBox.X > p1.upperBBox.X)
                return false;
            if (p2.lowerBBox.Y > p1.upperBBox.Y)
                return false;

            if (p2.upperBBox.X < p1.lowerBBox.X)
                return false;
            if (p1.lowerBBox.Y > p2.upperBBox.Y)
                return false;

            return true;
        }

        static int BinarySearch(Polygon[] polygons, float maxPoint)
        {
            int left = 0;
            int right = polygons.Length;

            int middle = 0;
            while (left < right)
            {

                middle = (left + right) / 2;
                /*Find the first polygon whose lowerbox is greater than or equal to the upper of base */

                /* if the upper of base is less than lower of overlay, ignore right part */
                if (maxPoint < polygons[middle].lowerBBox.X)
                {
                    right = middle - 1;
                }

                /* otherwise, left can be expanded*/
                else if (maxPoint >= polygons[middle].lowerBBox.X)
                {
                    left = middle + 1;
                }
            }

            return middle;
        }

        public static string PolygonToString(Polygon polygon)
        {
            StringBuilder Result = new StringBuilder();
            bool wroteFeature = false;

            VertexList[] ContourList = polygon.Contour;
            StringBuilder posList = new StringBuilder("");
            bool continueFeature = false;
            if (ContourList == null)
                return "";
            for (int i = 0; i < ContourList.Length; i++)
            {
                Vertex[] pointList = ContourList[i].Vertex;

                //Read the co-ordinates first 
                foreach (Vertex vx in pointList)
                {
                    try
                    {
                        posList.Append(vx.X.ToString() + " " + vx.Y.ToString() + " ");
                    }
                    catch
                    {
                        posList.Clear();
                        System.Diagnostics.Trace.WriteLine("Error reading co-ordinates");
                    }
                }

                /* append the first point at the end too */
                if (pointList.Length > 1)
                {
                    posList.Append(pointList[0].X.ToString() + " " + pointList[0].Y.ToString() + " ");
                }

                if (String.CompareOrdinal(posList.ToString(), "") != 0)
                {
                    /* PosList is not null. Start writing the feature if not already done */
                    if (continueFeature == false)
                    {
                        Result.AppendLine("<gml:featureMember>");
                        Result.AppendLine("<fme:polygonout>");
                        Result.AppendLine("<fme:FID>" + polygon.fID + "</fme:FID>");
                        Result.AppendLine("<fme:TYPE>" + polygon.polyType + "</fme:TYPE>");
                        Result.AppendLine("<gml:surfaceProperty>");
                        Result.AppendLine("<fme:Surface " + "srsName=" + '"' + polygon.srsname + '"' + " srsDimension=" + '"' + polygon.srsDimension + '"' + ">");
                        Result.AppendLine("<gml:patches>");
                        Result.AppendLine("<gml:PolygonPatch>");
                        continueFeature = true;
                        wroteFeature = true;
                    }

                    if (polygon.ContourIsHole[i] == true)
                    {
                        Result.AppendLine("<gml:interior>");
                    }
                    else
                    {
                        Result.AppendLine("<gml:exterior>");
                    }

                    Result.AppendLine("<gml:LinearRing>");
                    Result.AppendLine("<gml:posList>" + posList.ToString() + "</gml:posList>");
                    posList.Clear();
                    Result.AppendLine("</gml:LinearRing>");

                    if (polygon.ContourIsHole[i] == true)
                    {
                        Result.AppendLine("</gml:interior>");
                    }
                    else
                    {
                        Result.AppendLine("</gml:exterior>");
                    }
                }
            }

            if (wroteFeature == true)
            {
                /* End the tags */
                Result.AppendLine("</gml:PolygonPatch>");
                Result.AppendLine("</gml:patches>");
                Result.AppendLine("</gml:Surface>");
                Result.AppendLine("</gml:surfaceProperty>");
                Result.AppendLine("</fme:polygonout>");
                Result.AppendLine("</gml:featureMember>");
            }

            //System.Diagnostics.Trace.WriteLine(Result.ToString());
            return Result.ToString();
        }

        public static Polygon PolygonFromString(string task)
        {
            int cntr = 0;
            string[] overlayTask = task.Split('[');
            Polygon polygonTask = parseString(overlayTask[0]);

            for (cntr = 1; cntr < overlayTask.Length; cntr++)
            {
                polygonTask.LinkPolygon(parseString(overlayTask[cntr]));
            }

            return polygonTask;
        }

        public static Polygon parseString(string polygon)
        {
            bool ishole = true;
            Polygon outPolygon = new Polygon();
            string orig_polygon = polygon;
            polygon = polygon.Replace("gml:", null);
            polygon = polygon.Replace("fme:", null);
            XmlTextReader myParser = new XmlTextReader(new StringReader(polygon));
            while (myParser.Read())
            {
                /* There are two kind of elements in the world. The substantial ones called polygons,
                    * and the hideous ones called holes. Both have pos lists but the element outside 
                    * Linearring appears to be different. It is exterior for the polygons and interior 
                    * for the holes. A good man should read both and discriminate*/

                if (myParser.Name.Contains("FID") && myParser.IsStartElement())
                {
                    outPolygon.fID = myParser.ReadString();
                }

                if (myParser.Name.Contains("Surface") && myParser.IsStartElement())
                {
                    outPolygon.srsname = myParser[0];
                    outPolygon.srsDimension = myParser[1];
                }

                if (myParser.Name.Contains("exterior"))
                {
                    ishole = false;
                    continue;
                }

                if (myParser.Name.Contains("interior"))
                {
                    ishole = true;
                    continue;
                }

                /* if this is the list of points  - Bingo */
                if (myParser.Name.Contains("posList"))
                {
                    PointF[] vertices = parsePts(myParser.ReadString()).ToArray();
                    outPolygon.AddContour(new VertexList(vertices), ishole);
                }
            }
            return outPolygon;
        }


    }
}



